package cn.com.citydo.controller;

import cn.com.citydo.common.core.universal.entity.OutUserVO;
import cn.com.citydo.common.core.universal.entity.ResultVO;
import cn.com.citydo.common.core.universal.exception.SysException;
import cn.com.citydo.ec.starter.properties.SdkProperties;
import cn.com.citydo.ec.starter.properties.UnifiedLoginUrlConfig;
import cn.com.citydo.ec.starter.service.AppAuthService;
import cn.com.citydo.ec.starter.service.UnifiedLoginService;
import cn.com.citydo.ec.starter.utils.MyHttpSessionHolder;
import cn.com.citydo.ec.starter.utils.SignUtil;
import cn.dev33.satoken.stp.SaTokenInfo;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
/**
 * 统一登录实例
 */

@Slf4j
@EnableScheduling
@RestController()
@RequestMapping("/client")
public class SsoClientController {

	private static final String tokenInfo = "tokenInfo";

	private final AtomicReference<String> currentAppAccessToken = new AtomicReference<>(null);

	@Resource
	AppAuthService appAuthService;

	@Resource
	SdkProperties sdkProperties;
	
	@Resource
	UnifiedLoginService unifiedLoginService;

	/**
	 * 定时刷新token ,一小时过期，所以需要定时刷新
	 */
	@Scheduled(fixedRate = 1000*3600)
	public  void flushToken(){
		SaTokenInfo tokenInfo = appAuthService.getTokenInfo(sdkProperties.getAppKey(), sdkProperties.getAppSecret(),sdkProperties.getApiAddress());
		if (ObjectUtil.isNotEmpty(tokenInfo)){
			currentAppAccessToken.set(tokenInfo.tokenValue);
			log.info("刷新appToken成功:{}", tokenInfo.tokenValue);
		}
	}
	/**
	 *
	 *首页实例
	 */
	@RequestMapping("/sso/test")
	public String index(HttpSession session) {
        return "<h2>Sa-Token SSO-Client 应用端</h2>" +
				"<p>当前会话登录账号：" + session.getAttribute(tokenInfo) + "</p>" +
				"<p><a href=\"javascript:location.href='/client/sso/login?back=' + encodeURIComponent(location.href);\">登录</a>" +
				" <a href='/client/sso/logOut?back=' +  + encodeURIComponent(location.href);>注销</a>" +
				" <a href='/client/sso/myinfo' target=\"_blank\">获取资料</a></p>";
	}

	/**
	 *
	 *SSO-Client端：单点登录地址
	 */
	@RequestMapping("/sso/login")
	public void ssoLogin(String ticket, @RequestParam(defaultValue = "/") String back,
			HttpServletRequest request, HttpServletResponse response, HttpSession session) throws IOException {
		
		// 如果已经登录，则直接返回 
		if(session.getAttribute(tokenInfo) != null) {
			response.sendRedirect(back);
			return;
		}
		/*
		 * 此时有两种情况: 
		 * 情况1：ticket无值，说明此请求是Client端访问，需要重定向至SSO认证中心 
		 * 情况2：ticket有值，说明此请求从SSO认证中心重定向而来，需要根据ticket进行登录 
		 */
		if(ticket == null) {
			String currUrl = request.getRequestURL().toString();
			String clientLoginUrl = currUrl + "?back=" + SignUtil.encodeUrl(back);
			String serverAuthUrl = sdkProperties.getViewAddress()+ UnifiedLoginUrlConfig.ssoAuthUrl + "?client_id="+sdkProperties.getClientId()+"&redirect=" + clientLoginUrl;
			response.sendRedirect(serverAuthUrl);
		} else {
			// 获取当前 client 端的单点注销回调地址 
			String ssoLogoutCall = "";
			if(sdkProperties.getIsSlo()) {
				ssoLogoutCall = request.getRequestURL().toString().replace("/sso/login", "/sso/logoutCall"); 
			}
			// 校验 ticket
			Map<String, Object> tokenInfo = unifiedLoginService.ssoCheckTicket(sdkProperties.getClientId(), ticket, ssoLogoutCall, currentAppAccessToken.get());
			session.setAttribute("tokenInfo", tokenInfo);
			// 返回 back 地址
			response.sendRedirect(back);
		}
	}
	
	// SSO-Client端：单点注销地址
	@RequestMapping("/sso/logOut")
	public ResultVO<String> ssoLogout(@RequestParam(defaultValue = "/") String back,
			HttpServletResponse response, HttpSession session) throws IOException {
		Object tokenInfo = session.getAttribute("tokenInfo");
		// 如果未登录，则无需注销 
        if(ObjectUtil.isEmpty(tokenInfo)) {
			response.sendRedirect(back);
			log.info("未登录，则无需注销！");
			return ResultVO.success("未登录，则无需注销！");
        }
        // 调用 sso-server 认证中心单点注销API
		// token信息
		JSONObject tokenInfoObj = JSONUtil.parseObj(tokenInfo);
		Boolean isLogOut = unifiedLoginService.ssoSignOut(tokenInfoObj.getStr("loginId"), currentAppAccessToken.get());
		// 校验响应状态码，200 代表成功
		if(isLogOut) {
	        // 极端场景下，sso-server 中心的单点注销可能并不会通知到此 client 端，所以这里需要再补一刀
			session.removeAttribute("tokenInfo");
			// 返回 back 地址
			response.sendRedirect(back);
			return ResultVO.success("单点注销成功！");
		} else {
			log.error("单点注销失败！");
			throw new SysException("单点注销失败！");
		}
	}
	/**
	 * 单点注销回调地址
	 *
	 * @param loginId loginId
	 * @param request request
	 * @return ResultVO
	 */
	@RequestMapping("/sso/logoutCall")
	public ResultVO<String> ssoLogoutCall(String loginId,HttpServletRequest request) {
		String headAccessToken =request.getHeader("Access-Token");
		if(headAccessToken!=null){
			if(!Objects.equals(currentAppAccessToken.get(),headAccessToken)){
				throw new SysException("应用token校验不通过，不允许退出!");
			}
		}
		// 获取所有活跃会话（返回的是不可修改的安全视图）
		Map<String, HttpSession> sessions = MyHttpSessionHolder.getActiveSessions();
		// 并行流处理提升遍历效率（根据需求选择是否并行）
		sessions.values().parallelStream().forEach(session -> {
			// 双重空值检查确保线程安全
			if (session != null) {
				Object tokenInfo = session.getAttribute("tokenInfo");
				JSONObject tokenInfoObj = JSONUtil.parseObj(tokenInfo);
				if (tokenInfo != null && Objects.equals(tokenInfoObj.getStr("loginId"), loginId)) {
					// 同步块保证原子操作
					synchronized (session) {
						// 操作1: 清除token信息
						session.removeAttribute("tokenInfo");
						// 操作2: 立即销毁会话（根据业务需求二选一）
						// session.invalidate();  // 会触发sessionDestroyed事件自动清理
					}
				}
			}
		});
		return ResultVO.success("单点注销回调成功！");
	}


	@RequestMapping("/sso/myinfo")
	public ResultVO<Object> myinfo(HttpSession session) {
		if(session.getAttribute("tokenInfo") == null) {
			return ResultVO.fail("未登录，无法获取!");
		}
		Object tokenInfo = session.getAttribute("tokenInfo");
		JSONObject tokenInfoObj = JSONUtil.parseObj(tokenInfo);
		// 获取当前登录账号的token
		String userToken =  tokenInfoObj.getStr("accessToken");
		//根据用户token获取用户信息
		OutUserVO outUserVO = unifiedLoginService.ssoUserinfo(userToken, currentAppAccessToken.get());
		return ResultVO.success(outUserVO);
	}

}
